#ifndef __DS_SHAPES__
#define __DS_SHAPES__

#include <string.h>
#include <map>

#include <boost/shared_ptr.hpp>

#include <bullet/btBulletCollisionCommon.h>
#include <bullet/btBulletDynamicsCommon.h>
#include <bullet/BulletCollision/CollisionShapes/btShapeHull.h>

namespace ds {

class Shapes {
private:
    static std::map<std::string, boost::shared_ptr<btCollisionShape> > shapes;

public:
    /// Get specified shape.
    inline static boost::shared_ptr<btCollisionShape> get(const std::string &name) {
        std::map<std::string, boost::shared_ptr<btCollisionShape> >::iterator iter = shapes.find(name);
        if (iter != shapes.end())
            return iter->second;
        return boost::shared_ptr<btCollisionShape>();
    }

    /// Returns true if the specified shape exists.
    inline static bool has(const std::string &name) {
        std::map<std::string, boost::shared_ptr<btCollisionShape> >::iterator iter = shapes.find(name);
        if (iter != shapes.end())
            return true;
        return false;
    }

    /// Replace shape.
    inline static void set(const std::string &name, boost::shared_ptr<btCollisionShape> shape) {
        shapes.insert(std::pair<std::string, boost::shared_ptr<btCollisionShape> >(name, shape));
    }

    /// Adds a shape only if it isn't already added.
    inline static void add(const std::string &name, boost::shared_ptr<btCollisionShape> shape) {
        std::map<std::string, boost::shared_ptr<btCollisionShape> >::iterator iter = shapes.find(name);
        if (iter == shapes.end())
            shapes.insert(std::pair<std::string, boost::shared_ptr<btCollisionShape> >(name, shape));
    }

    /// Removes all shapes.
    inline static void clear() {
        shapes.clear();
    }

    /// Creates a collision shape from JSL node and model (model is needed if shape is created from vertex data).
    inline static btCollisionShape* createFromJSL(jsl::Node* node, egl::video::Model* model = NULL) {
        if (node == NULL)
            return NULL;
        std::string type = node->at("type")->asString();
        if (type == "compound") {
            btCompoundShape* shape = new btCompoundShape();
            jsl::Array &shapes = *node->at("compound")->asArray();
            btMatrix3x3 tmpbasis;
            btVector3 tmporigin;
            try {
                for (unsigned int i = 0; i < shapes.size(); i++) {
                    jsl::Node* tmp = shapes[0]->at("local");
                    if (tmp->type() == jsl::StringType) {
                    	if (tmp->asString() == "identity") {
                    		tmpbasis.setIdentity();
                    		tmporigin.setValue(btScalar(0.0), btScalar(0.0), btScalar(0.0));
                    	}
                    } else {
                        try {
                        	jsl::Array &local = *tmp->asArray();
                            tmpbasis.setValue(
                                local[0]->asDouble(), local[4]->asDouble(), local[8]->asDouble(),
                                local[1]->asDouble(), local[5]->asDouble(), local[9]->asDouble(),
                                local[2]->asDouble(), local[6]->asDouble(), local[10]->asDouble());
                            tmporigin.setValue(
                            	local[12]->asDouble(), local[13]->asDouble(), local[14]->asDouble());
                        } catch (std::exception &e) {
                            tmpbasis.setIdentity();
                            tmporigin.setValue(btScalar(0.0), btScalar(0.0), btScalar(0.0));
                        }
                    }
                    tmp = shapes[i]->at("shape");
                    btCollisionShape* childShape = createFromJSL(tmp, model);
                    if (childShape != NULL)
                        shape->addChildShape(btTransform(tmpbasis, tmporigin), childShape);
                }
            } catch (std::exception &e) {
                delete shape;
                return NULL;
            }
            return shape;
        } else if (type == "convexHull") {
            if (model == NULL)
                return NULL;
            jsl::Node* tmp = node->at("mesh");
            if (tmp == NULL || tmp->asString() == "")
                return NULL;
            egl::video::Mesh* mesh = model->getMesh(tmp->asString());
            if (mesh == NULL)
                return NULL;
            btConvexHullShape* tmpchull = new btConvexHullShape(mesh->vertexes, mesh->vertexCount, sizeof(mesh->vertexes[0]) * 3);
            btShapeHull* tmphull = new btShapeHull(tmpchull);
            tmphull->buildHull(tmpchull->getMargin());
            btConvexHullShape* shape = new btConvexHullShape((btScalar*)tmphull->getVertexPointer(), tmphull->numVertices());
            delete tmphull;
            delete tmpchull;
            return shape;
        } else if (type == "sphere") {
            btScalar radius;
            radius = node->at("radius")->asDouble();
            return new btSphereShape(radius);
        } else if (type == "box") {
            btScalar xext, yext, zext;
            xext = node->at("xHalfExtent")->asDouble();
            yext = node->at("yHalfExtent")->asDouble();
            zext = node->at("zHalfExtent")->asDouble();
            return new btBoxShape(btVector3(xext, yext, zext));
        }
        return NULL;
    }
};

std::map<std::string, boost::shared_ptr<btCollisionShape> > Shapes::shapes;

} // namespace ds

#endif
